﻿using System;
using System.Collections.Generic;
using Moq;
using NUnit.Framework;

namespace PoolKit.Tests
{
    [TestFixture]
    public class ResourceCollectionTests
    {
        private static readonly ResourceSelectionStrategy<string> ResourceSelectionStrategyStub =
            new Mock<ResourceSelectionStrategy<string>>().Object;

        [Test]
        public void MakeSureResourceCollectionCannotBeConstructedByPassingNullAsSetOfResources()
        {
            Assert.Throws<ArgumentNullException>(delegate()
                              {
                                  new ResourceCollection<string>(null, ResourceSelectionStrategyStub);
                              });
        }

        [Test]
        public void MakeSureResourceCollectionThrowsIfDuplicateResourcesAreGivenDuringConstruction()
        {
            Assert.Throws<ArgumentException>(delegate()
            {
                new ResourceCollection<string>(new string[] { "one", "one", "one", "two", "three" }, ResourceSelectionStrategyStub);
            });
        }

        [Test]
        public void MakeSureResourceCollectionOnlyGivesAwayAsMuchAsItContains()
        {
            var resourcesToPool = new string[]{"one", "two", "three", "four", "five"};

            var resourceCollection = new ResourceCollection<string>(resourcesToPool, new FirstElementSelectionStrategy<string>());
            
            List<string> resourcesTaken = resourceCollection.Drain();

            CollectionAssert.AreEquivalent(resourcesToPool, resourcesTaken);
        }

        [Test]
        public void MakeSureReturningResourceToCollectionMakesItEligibleForGiveawayAgain()
        {
            var resourcesToPool = new string[] { "one" };

            var resourceCollection = new ResourceCollection<string>(resourcesToPool, new FirstElementSelectionStrategy<string>());

            string resource1;
            Assert.IsTrue(resourceCollection.TryTake(out resource1));

            string resourceTemp;
            Assert.IsFalse(resourceCollection.TryTake(out resourceTemp));

            resourceCollection.AcceptBack(resource1);

            string resource2;
            Assert.IsTrue(resourceCollection.TryTake(out resource2));
            Assert.AreSame(resource1, resource2);
        }

        [Test]
        public void MakeSureCollectionDoesNotAcceptAResourceItHasForgotten()
        {
            var resourcesToPool = new string[] { "one" };

            var resourceCollection = new ResourceCollection<string>(resourcesToPool, new FirstElementSelectionStrategy<string>());

            string resource1;
            Assert.IsTrue(resourceCollection.TryTake(out resource1));

            resourceCollection.Forget(resource1);

            Assert.Throws<CannotAcceptResourceException>(
                () =>
                    {
                        resourceCollection.AcceptBack(resource1);
                    });
        }

        [Test]
        public void MakeSureCollectionCannotForgetResourceNotTakenFromThatCollection()
        {
            var resourcesToPool = new string[] { "one" };

            var resourceCollection = new ResourceCollection<string>(resourcesToPool, new FirstElementSelectionStrategy<string>());

            Assert.Throws<CannotForgetResourceException>(
                () =>
                {
                    resourceCollection.Forget("two");
                });
        }

        [Test]
        public void MakeSureCollectionCannotSubstituteAForgottenResource()
        {
            var resourcesToPool = new string[] { "one" };

            var resourceCollection = new ResourceCollection<string>(resourcesToPool, new FirstElementSelectionStrategy<string>());

            string resource1;
            resourceCollection.TryTake(out resource1);

            resourceCollection.Forget(resource1);

            Assert.Throws<CannotAcceptResourceException>(
                () =>
                {
                    resourceCollection.AcceptSubstitution(resource1, "six");
                });
        }

        [Test]
        public void MakeSureCollectionCannotForgetResourceTwice()
        {
            var resourcesToPool = new string[] { "one" };

            var resourceCollection = new ResourceCollection<string>(resourcesToPool, new FirstElementSelectionStrategy<string>());

            string resource1;
            resourceCollection.TryTake(out resource1);

            resourceCollection.Forget(resource1);

            Assert.Throws<CannotForgetResourceException>(
                () =>
                {
                    resourceCollection.Forget(resource1);
                });
        }

        [Test]
        public void MakeSureReturningResourcesNotComingFromCollectionProducesAnException()
        {
            var resourcesToPool = new string[] { "one" };

            var resourceCollection = new ResourceCollection<string>(resourcesToPool, new FirstElementSelectionStrategy<string>());

            Assert.Throws<CannotAcceptResourceException>(delegate()
            {
                resourceCollection.AcceptBack(Guid.NewGuid().ToString("N"));
            });
        }

        [Test]
        public void MakeSureReturningAResourceTwiceProducesAnException()
        {
            var resourcesToPool = new string[] { "one" };

            var resourceCollection = new ResourceCollection<string>(resourcesToPool, new FirstElementSelectionStrategy<string>());

            string resource;
            Assert.IsTrue(resourceCollection.TryTake(out resource));

            resourceCollection.AcceptBack(resource);

            Assert.Throws<CannotAcceptResourceException>(delegate()
            {
                resourceCollection.AcceptBack(resource);
            });
        }

        [Test]
        public void MakeSureReturningAResourceAfterFirstUnsuccessfulAttemptIsStillPossible()
        {
            var resourcesToPool = new string[] { "one", "two" };

            var resourceCollection = new ResourceCollection<string>(resourcesToPool, new FirstElementSelectionStrategy<string>());

            string resource1;
            Assert.IsTrue(resourceCollection.TryTake(out resource1));
            Assert.AreSame("one", resource1);

            Assert.Throws<ReplacingResourceProducesDuplicateException>(delegate()
            {
                resourceCollection.AcceptSubstitution("one", "two");
            });

            resourceCollection.AcceptSubstitution("one", "three");

            CollectionAssert.AreEquivalent(new[]{"two", "three"}, resourceCollection.Drain());
        }

        [Test]
        public void MakeSureReturningSubstitutionForAResourceTwiceProducesAnException()
        {
            var resourcesToPool = new string[] { "one" };

            var resourceCollection = new ResourceCollection<string>(resourcesToPool, new FirstElementSelectionStrategy<string>());

            string resource;
            Assert.IsTrue(resourceCollection.TryTake(out resource));

            resourceCollection.AcceptSubstitution(resource, "two");

            Assert.Throws<CannotAcceptResourceException>(delegate()
            {
                resourceCollection.AcceptSubstitution(resource, "two");
            });
        }

        [Test]
        public void MakeSureReturningNullsToCollectionProducesAnException()
        {
            var resourceCollection = new ResourceCollection<string>(new string[0], new FirstElementSelectionStrategy<string>());

            Assert.Throws<ArgumentNullException>(delegate()
            {
                resourceCollection.AcceptBack(null);
            });
        }

        [Test]
        public void MakeSureResourceCollectionThrowsIfNullResourcesAreGivenDuringConstruction()
        {
            Assert.Throws<ArgumentException>(delegate()
            {
                new ResourceCollection<string>(new string[]{null, "1", "2"}, new FirstElementSelectionStrategy<string>());
            });
        }

        [Test]
        public void MakeSureDeclaringThatResourceAlreadyInCollectionIsAboutToBeReturnedProducesAnException()
        {
            var resourceCollection = new ResourceCollection<string>(new string[] { "one" }, new FirstElementSelectionStrategy<string>());

            Assert.Throws<InvalidOperationException>(delegate()
            {
                resourceCollection.ReserveRightToReturnResource("one");
            });
        }

        [Test]
        public void MakeSureReservingRightToReturnStuffNotRelatedToCollectionThrows()
        {
            var resourceCollection = new ResourceCollection<string>(new string[] { "one" }, new FirstElementSelectionStrategy<string>());

            Assert.Throws<InvalidOperationException>(delegate()
            {
                resourceCollection.ReserveRightToReturnResource(Guid.NewGuid().ToString("N"));
            });
        }

        [Test]
        public void MakeSureCollectionThrowsWhenAskedToReplaceSomethingItDoesNotContain()
        {
            var resourceCollection = new ResourceCollection<string>(new string[] { "one" }, new FirstElementSelectionStrategy<string>());

            Assert.Throws<CannotAcceptResourceException>(delegate()
            {
                resourceCollection.AcceptSubstitution("two", "three");
            });
        }

        [Test]
        public void MakeSureCollectionAcceptsSubstitutionsWhenAskedTo()
        {
            var resourceCollection = new ResourceCollection<string>(new string[] { "one" }, new FirstElementSelectionStrategy<string>());

            string resource;
            resourceCollection.TryTake(out resource);

            resourceCollection.AcceptSubstitution(resource, "two");

            var collectionContents = resourceCollection.Drain();

            CollectionAssert.AreEquivalent(new[]{"two"}, collectionContents);
        }

        [Test]
        public void MakeSureCollectionCannotAcceptSubstitutionsIfTheyProduceDuplicatesInCollection()
        {
            var resourceCollection = new ResourceCollection<string>(new string[] { "one", "two" }, new FirstElementSelectionStrategy<string>());

            string resource; // one
            resourceCollection.TryTake(out resource);

            Assert.Throws<ReplacingResourceProducesDuplicateException>(
                delegate()
                    {
                        resourceCollection.AcceptSubstitution(resource, "two");
                    });
        }

        [Test]
        public void MakeSureCollectionCanAcceptBackResourcesRegisteredAsTaken()
        {
            var resourceCollection = new ResourceCollection<string>(new string[] { "one", "two" }, new FirstElementSelectionStrategy<string>());

            string resource; // one
            resourceCollection.TryTake(out resource);

            resourceCollection.RegisterResourceAsTaken("three");

            resourceCollection.AcceptBack("three");

            var collectionContents = resourceCollection.Drain();

            CollectionAssert.AreEquivalent(new[]{"two", "three"}, collectionContents);
        }

        [Test]
        public void MakeSureCollectionCannotGiveAwayResourcesRegisteredAsTakenUnlessTheyHaveBeenReturned()
        {
            var resourceCollection = new ResourceCollection<string>(new string[] { "one", "two" }, new FirstElementSelectionStrategy<string>());

            resourceCollection.RegisterResourceAsTaken("three");

            var collectionContents = resourceCollection.Drain();

            CollectionAssert.AreEquivalent(new[] { "two", "one" }, collectionContents);

            resourceCollection.AcceptBack("three");

            collectionContents = resourceCollection.Drain();

            CollectionAssert.AreEquivalent(new[] { "three" }, collectionContents);
        }

        [Test]
        public void MakeSureCollectionCanReplaceResourcesRegisteredAsTaken()
        {
            var resourceCollection = new ResourceCollection<string>(new string[] { "one", "two" }, new FirstElementSelectionStrategy<string>());

            resourceCollection.RegisterResourceAsTaken("three");

            resourceCollection.AcceptSubstitution("three", "four");

            var collectionContents = resourceCollection.Drain();

            CollectionAssert.AreEquivalent(new[] { "two", "one", "four" }, collectionContents);
        }

        [Test]
        public void MakeSureCollectionThrowsWhenItSeesAReplacedResourceBeingReturned()
        {
            var resourceCollection = new ResourceCollection<string>(new string[] { "one" }, new FirstElementSelectionStrategy<string>());

            string resource; // one
            resourceCollection.TryTake(out resource);

            resourceCollection.AcceptSubstitution(resource, "two");

            Assert.Throws<CannotAcceptResourceException>(
                delegate()
                {
                    resourceCollection.AcceptBack(resource);
                });
        }
    }
}